package com.bestcem.xm.qdes.handler.export;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.bestcem.xm.common.core.exception.BusinessException;
import com.bestcem.xm.qdes.entity.QdesProject;
import com.bestcem.xm.qdes.entity.QdesQstruct;
import com.bestcem.xm.qdes.handler.export.type.SingleHandler;
import com.bestcem.xm.qdes.mapper.QdesProjectMapper;
import com.bestcem.xm.qdes.mapper.QdesQstructMapper;
import com.bestcem.xm.qdes.service.QdesProjectService;
import com.bestcem.xm.qdes.service.QdesQuestionLibService;
import com.bestcem.xm.qdes.service.QdesQuestionService;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblWidth;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.bestcem.xm.qdes.enums.QuestionTypeEnum.*;
import static com.bestcem.xm.qdes.util.QdesConstant.QUESTION_TYPE_CASCADE;
import static com.bestcem.xm.qdes.util.QdesConstant.QUESTION_TYPE_EVALUATION;
import static com.bestcem.xm.qdes.util.QdesConstant.QUESTION_TYPE_MULTIPLE;
import static com.bestcem.xm.qdes.util.QdesConstant.QUESTION_TYPE_SINGLE;
import static com.bestcem.xm.qdes.util.QdesConstant.*;
import static com.bestcem.xm.qdes.util.QstructConstant.INDIVIDUAL_EXCEL_EXPORT_HEADERS;

@Slf4j
@Component
@RequiredArgsConstructor
public class ProjectExportHandler {

    private final static Pattern P_PATTERN = Pattern.compile("(<p.*?>)|(</p>)");
    private final static String UNDERLINE = "_";
    private final static String SINGLE_SIGN = "○ ";
    private final static String MULTI_SIGN = "□ ";
    private final static String SINGLE_SEL_SIGN = "◉";
    private final static String MULTI_SEL_SIGN = "▣";

    private @NonNull QdesQstructMapper qdesQstructMapper;
    private @NonNull QdesProjectMapper qdesProjectMapper;
    private @NonNull QdesQuestionService questionService;
    private @NonNull QdesQuestionLibService questionlibService;
    private @NonNull QdesProjectService qdesProjectService;
    private @NonNull List<BaseTypeHandler> baseTypeHandlerList;


    private List<List<String>> getProjectHeader() {
        List<List<String>> headers = new ArrayList<>();
        if (CollUtil.isNotEmpty(INDIVIDUAL_EXCEL_EXPORT_HEADERS)) {
            INDIVIDUAL_EXCEL_EXPORT_HEADERS.forEach(name -> {
                List<String> head = new ArrayList<>();
                head.add(name);
                headers.add(head);
            });
        }
        return headers;
    }

    private void dealAddress(List<String> address) {
        if (address.size() < 4) {
            int num = 4 - address.size();
            for (int i = 0; i < num; i++) {
                address.add("");
            }
        }
    }

    public void exportToExcel(ExportContext context, HttpServletResponse response) {
        QdesProject project = qdesProjectMapper.selectById(context.getProjectId());
        if (project == null) {
            throw new BusinessException("The project is not exist");
        }

        // 获取文件名称
        String fileName = project.getCode() + ".xls";
        // 获取表头
        List<List<String>> heads = getProjectHeader();

        // 响应设置
        try {
            response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8")); // 这种方式mac和windows导出的文件名就不会乱码
            response.setCharacterEncoding("utf-8");
            response.setContentType("application/octet-stream");
            response.addHeader("Content-Transfer-Encoding", "binary");
        } catch (UnsupportedEncodingException e) {
            log.error("Now encode error");
            throw new BusinessException("转码失败");
        }
        ServletOutputStream outputStream = null;
        try {
            response.setStatus(HttpStatus.OK.value());
            outputStream = response.getOutputStream();
            ExcelWriter build = EasyExcel.write(outputStream)
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    .useDefaultStyle(false)
                    .head(heads)
                    .build();
            WriteSheet writeSheet = EasyExcel.writerSheet().sheetName("sheet").build();
            List<List<String>> contents = prepareProjectExcelData(context);
            build.write(contents, writeSheet);
            build.finish();
            outputStream.flush();
        } catch (IOException e) {
            log.error("Now encode error");
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException ex) {
                    log.error("Close outputStream error");
                }
            }
        }
    }

    private List<List<String>> prepareProjectExcelData(ExportContext context) {
        List<List<String>> contents = new ArrayList<>();
        QdesProject project = qdesProjectMapper.selectById(context.getProjectId());
        if (project == null) {
            return contents;
        }

        LambdaQueryWrapper<QdesQstruct> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(QdesQstruct::getProjectId, context.getProjectId())
                .eq(QdesQstruct::getVersion, project.getVersion()).last("limit 1");
        QdesQstruct qdesQstruct = qdesQstructMapper.selectOne(queryWrapper);
        if (qdesQstruct == null) {
            return contents;
        }
        qdesQstruct.setData(questionlibService.fillDataQuestions(qdesQstruct.getId(), qdesQstruct.getData()));

        JSONObject dataJson = JSONObject.parseObject(qdesQstruct.getData());
        qdesProjectService.resetSeq(dataJson);
        List<String> attrs = Arrays.asList("showQuestionCid", "openCustomCid");
        String customAttr = project.getCustomAttr();
        if (StringUtils.isNotEmpty(customAttr)) {
            JSONObject jsonAttr = JSONObject.parseObject(customAttr);
            dataJson.put("limit_device", jsonAttr.getOrDefault("devOnce", false));
            attrs.forEach(attr -> dataJson.put(attr, jsonAttr.getOrDefault(attr, false)));
        } else {
            attrs.forEach(attr -> dataJson.put(attr, false));
        }
        CidGid cidGid = getQuestionCidGidDictByProject(dataJson, false);
        JSONObject dataJs = dataJson;
        dataJs = Optional.ofNullable(dataJs).orElse(new JSONObject());
        JSONObject relatedRefDict = dataJs.getJSONObject("relatedRefs");
        relatedRefDict = Optional.ofNullable(relatedRefDict).orElse(new JSONObject());

        JSONArray qList = new JSONArray();
        questionService.getQstructQuestions(dataJs, new HashMap<>(), qList);

        for (int index = 0; index < qList.size(); index++) {
            JSONObject q = qList.getJSONObject(index);
            String qtype = q.getString("qtype");
            String customQtype = q.getString("custom_qtype");

            if ((QUESTION_TYPE_CASCADE.equals(qtype) || QUESTION_TYPE_EVALUATION.equals(qtype))
                    || (QUESTION_TYPE_SINGLE.equals(qtype) && QUESTION_CUSTOM_TYPE_IMAGE_SELECT.equals(customQtype))
                    || (QUESTION_TYPE_MULTIPLE.equals(qtype) && QUESTION_CUSTOM_TYPE_IMAGE_SELECT.equals(customQtype))) {
                // 暂不支持图片选择题、级联题、评价题
                continue;
            }

            BaseTypeHandler typeHandler = baseTypeHandlerList.stream().filter(it -> it.getType().equals(qtype)).findFirst().orElse(SpringUtil.getBean(SingleHandler.class));
            String gidstr = q.getString("gid");

            List<JSONObject> relatedRefs = typeHandler.castList(relatedRefDict.get(gidstr), JSONObject.class);
            List<List<String>> items = getResultItems(typeHandler.getItems(q, cidGid.getQgidDict(), relatedRefs));
            contents.addAll(items);
            contents.add(new ArrayList<>());
        }
        return contents;
    }

    private CidGid getQuestionCidGidDictByProject(JSONObject dataJson, boolean needOptionRelated) {
        // 根据project获取题目信息
        CidGid cidGid = new CidGid();
        List<JSONObject> questions = qdesProjectService.walkQstructQuestions(dataJson);
        JSONObject cidMap = new JSONObject();
        JSONObject gidMap = new JSONObject();
        questions.forEach(q -> {            // 选项关联，导出项目，去掉被关联过来的选项
            if (!needOptionRelated) {
                JSONArray itemArray = q.getJSONArray("items");
                itemArray = Optional.ofNullable(itemArray).orElse(new JSONArray());
                JSONArray newItemArray = new JSONArray();
                itemArray.forEach(item -> {
                    if (StringUtils.isEmpty(((JSONObject) item).getString("option_origin"))) {
                        newItemArray.add(item);
                    }
                });
                q.put("items", newItemArray);
            }
            cidMap.put(q.getString("cid"), q);
            gidMap.put(q.getString("gid"), q);
        });
        cidGid.setQcidDict(cidMap);
        cidGid.setQgidDict(gidMap);
        return cidGid;
    }

    private List<List<String>> getResultItems(List<Object> oldItems) {
        List<List<String>> items = new ArrayList<>();
        if (CollectionUtils.isEmpty(oldItems)) {
            return items;
        }
        //BaseTypeHandler typeHandler = new SortHandler();
        BaseTypeHandler typeHandler = baseTypeHandlerList.stream().filter(it -> it.getType().equals("score")).findFirst().orElse(SpringUtil.getBean(SingleHandler.class));

        oldItems.forEach(item -> {
            if (item instanceof List) {
                List<String> itemsList = typeHandler.castList(item, String.class);
                List<String> lastList = new ArrayList<>();
                for (String oneItem : itemsList) {
                    if (oneItem.contains("[]")) {
                        lastList.add(oneItem.replace("[]", ""));
                    } else {
                        lastList.add(oneItem);
                    }
                }
                items.add(lastList);
            } else {
                items.add(Collections.singletonList((String) item));
            }
        });
        return items;
    }

    public void exportToWord(ExportContext context, HttpServletResponse response) {
        QdesProject project = qdesProjectMapper.selectById(context.getProjectId());
        if (project == null) {
            throw new BusinessException("The project is not exist");
        }
        // 获取文件名称
        String fileName = project.getCode() + ".docx";
        // 响应设置
        try {
            XWPFDocument document = generateWordXWPFDocument(context);
            response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8")); // 这种方式mac和windows导出的文件名就不会乱码
            response.setCharacterEncoding("utf-8");
            response.setContentType("application/vnd.ms-excel");
            response.addHeader("Content-Transfer-Encoding", "binary");
            OutputStream os = response.getOutputStream();
            document.write(os);
            os.close();
        } catch (UnsupportedEncodingException e) {
            log.error("Now encode error");
            throw new BusinessException("转码失败");
        } catch (IOException e) {
            log.error("Now IOException");
            throw new BusinessException("导出失败");
        }
    }

    private XWPFDocument generateWordXWPFDocument(ExportContext context) {
        XWPFDocument doc = new XWPFDocument();
        QdesProject project = qdesProjectMapper.selectById(context.getProjectId());
        if (project == null) {
            return doc;
        }

        LambdaQueryWrapper<QdesQstruct> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(QdesQstruct::getProjectId, context.getProjectId())
                .eq(QdesQstruct::getVersion, project.getVersion()).last("limit 1");
        QdesQstruct qdesQstruct = qdesQstructMapper.selectOne(queryWrapper);
        if (qdesQstruct == null) {
            return doc;
        }
        qdesQstruct.setData(questionlibService.fillDataQuestions(qdesQstruct.getId(), qdesQstruct.getData()));
        JSONObject dataJson = JSONObject.parseObject(qdesQstruct.getData());
        qdesProjectService.resetSeq(dataJson);
        List<String> attrs = Arrays.asList("showQuestionCid", "openCustomCid");
        String customAttr = project.getCustomAttr();
        if (StringUtils.isNotEmpty(customAttr)) {
            JSONObject jsonAttr = JSONObject.parseObject(customAttr);
            dataJson.put("limit_device", jsonAttr.getOrDefault("devOnce", false));
            attrs.forEach(attr -> dataJson.put(attr, jsonAttr.getOrDefault(attr, false)));
        } else {
            attrs.forEach(attr -> dataJson.put(attr, false));
        }

        JSONObject content = Optional.of(dataJson.getJSONObject("desc_begin_page"))
                .orElse(new JSONObject()).getJSONObject("content");
        if (content != null && StringUtils.isNotEmpty(content.getString("text"))) {
            createTitle(doc, smsContentHtmlLabelsRegexp(content.getString("text")));
        } else {
            createTitle(doc, project.getTitle());
        }
        doc.createParagraph();

        JSONObject desc = Optional.of(dataJson.getJSONObject("desc_begin_page"))
                .orElse(new JSONObject()).getJSONObject("desc");
        if (desc != null && StringUtils.isNotEmpty(desc.getString("text"))) {
            createFirstParagraph(doc, smsContentHtmlLabelsRegexp(desc.getString("text")));
        } else {
            createFirstParagraph(doc, "欢迎参加本次调查");
        }
        doc.createParagraph();
        walkItem(dataJson, doc);
        return doc;
    }

    private void walkItem(JSONObject item, XWPFDocument doc) {
        String typ = item.getString("type");
        if ("Q".equals(typ)) {
            if (item.containsKey("cid")) {
                genQuestion(item, doc);
            }
        } else if ("G".equals(typ)) {
            if (!item.containsKey("items")) {
                throw new BusinessException(String.format("group[gid=%s] must have :items field", item.get("gid")));
            }
            for (int i = 0; i < item.getJSONArray("items").size(); i++) {
                walkItem(item.getJSONArray("items").getJSONObject(i), doc);
            }
        }
    }

    private void genQuestion(JSONObject item, XWPFDocument doc) {
        String questionCid = item.getString("cid");
        if (StringUtils.isNotEmpty(questionCid)) {
            createFirstParagraph(doc, questionCid + ":" + item.getString("title"));
            String questionType = item.getString("qtype");
            if (questionType != null && questionType.contains("matrix")) {
                matrixQuestion(doc, item);
            } else {
                normalQuestion(doc, item);
            }
            doc.createParagraph();
        }
    }

    private void normalQuestion(XWPFDocument doc, JSONObject item) {
        JSONArray optionList = Optional.of(item.getJSONArray("items")).orElse(new JSONArray());
        String questionType = Optional.of(item.getString("qtype")).orElse("");
        if ("city".equals(questionType)) {
            Integer lv = item.getInteger("info_level");
            List<String> allList = Arrays.asList("省份", "城市", "区/县", "街道信息");
            for (int i = 0; i < lv; i++) {
                String content = allList.get(i) + " " + String.join("", Collections.nCopies(24, UNDERLINE));
                createParagraph(doc, content);
            }
        }
        for (int i = 0; i < optionList.size(); i++) {
            JSONObject option = optionList.getJSONObject(i);
            String sign = "";
            if ("single".equals(questionType)) {
                sign = " " + SINGLE_SIGN;
            } else if ("multiple".equals(questionType)) {
                sign = " " + MULTI_SIGN;
            }
            XWPFParagraph para = createParagraph(doc, sign);
            XWPFRun runText1 = para.createRun();
            runText1.setText(option.getString("title"));
            runText1.setFontSize(11);

            StringBuilder optionContent = new StringBuilder(" ");
            Boolean isOpen = option.getBoolean("is_open");
            switch (questionType) {
                case "score":
                    optionContent.append(String.join("", Collections.nCopies(5, UNDERLINE)));
                    break;
                case "sort":
                    optionContent.insert(0, String.join("", Collections.nCopies(5, UNDERLINE)));
                    break;
                case "multiple_blank":
                case "blank":
                    optionContent.append(String.join("", Collections.nCopies(24, UNDERLINE)));
                    break;
            }
            if (isOpen != null && isOpen) {
                optionContent.append(String.join("", Collections.nCopies(12, UNDERLINE)));
            }
            XWPFRun runText2 = para.createRun();
            runText2.setText(optionContent.toString());
            runText2.setFontSize(11);
        }
    }

    private void matrixQuestion(XWPFDocument doc, JSONObject item) {
        JSONArray optionList = Optional.of(item.getJSONArray("items")).orElse(new JSONArray());
        JSONArray matrixList = Optional.of(item.getJSONArray("rows_items")).orElse(new JSONArray());
        String questionType = Optional.of(item.getString("qtype")).orElse("");
        String positionContent = "option-temp";
        // 表格
        XWPFParagraph paragraph = doc.createParagraph();
        XWPFTable table = paragraph.getDocument().createTable(matrixList.size() + 1, optionList.size() + 1);
        table.setCellMargins(20, 0, 20, 1);
        //表格属性
        CTTblPr tablePr = table.getCTTbl().addNewTblPr();
        //表格宽度
        CTTblWidth width = tablePr.addNewTblW();
        width.setW(BigInteger.valueOf(0));

        if (QUESTION_TYPE_MATRIX_SCORE.getValue().equals(questionType)) {
            positionContent = UNDERLINE + UNDERLINE + UNDERLINE;
        }
        if (QUESTION_TYPE_MATRIX_BLANK.getValue().equals(questionType)) {
            positionContent = UNDERLINE + UNDERLINE + UNDERLINE + UNDERLINE + UNDERLINE;
        }
        if (QUESTION_TYPE_MATRIX_SINGLE.getValue().equals(questionType)) {
            positionContent = String.format(" %s ", SINGLE_SIGN);
        }
        if (QUESTION_TYPE_MATRIX_MULTIPLE.getValue().equals(questionType)) {
            positionContent = String.format(" %s ", MULTI_SIGN);
        }
        List<Integer> isOpenPosition = new ArrayList<>();
        for (int i = 0; i < table.getRows().size(); i++) {
            List<XWPFTableCell> tableCells = table.getRow(i).getTableCells();
            if (i == 0) {
                for (int j = 0; j < optionList.size(); j++) {
                    JSONObject option = optionList.getJSONObject(j);
                    if (j == 0) {
                        tableCells.get(0).setText("");
                    }
                    tableCells.get(j + 1).setText(option.getString("title"));
                    if (option.getBoolean("is_open")) {
                        isOpenPosition.add(j);
                    }
                }
            } else {
                for (int j = 0; j < tableCells.size(); j++) {
                    if (j == 0) {
                        tableCells.get(0).setText(matrixList.getJSONObject(i - 1).getString("title"));
                        continue;
                    }
                    if (isOpenPosition.contains(j - 1)) {
                        positionContent = positionContent + String.join("", Collections.nCopies(12, UNDERLINE));
                    } else {
                        positionContent = positionContent.replace(String.join("", Collections.nCopies(12, UNDERLINE)), "");
                    }
                    tableCells.get(j).setText(positionContent);
                }
            }
        }
    }


    private void createTitle(XWPFDocument doc, String content) {
        XWPFParagraph title = doc.createParagraph();
        title.setAlignment(ParagraphAlignment.CENTER);
        XWPFRun r1 = title.createRun();
        r1.setBold(true);
        r1.setFontFamily("宋体");
        r1.setText(content);
        r1.setFontSize(20);
    }

    private void createFirstParagraph(XWPFDocument doc, String content) {
        XWPFParagraph actType = doc.createParagraph();
        XWPFRun runText2 = actType.createRun();
        runText2.setBold(true);
        runText2.setText(content);
        runText2.setFontSize(12);
    }

    private XWPFParagraph createParagraph(XWPFDocument doc, String content) {
        XWPFParagraph actType = doc.createParagraph();
        XWPFRun runText2 = actType.createRun();
        runText2.setText(content);
        runText2.setFontSize(9);
        return actType;
    }

    private String smsContentHtmlLabelsRegexp(String smsContent) {
        Matcher matcher = P_PATTERN.matcher(smsContent);
        smsContent = matcher.replaceAll("");
        return smsContent;
    }

    @Data
    public static class CidGid {
        private JSONObject qcidDict;
        private JSONObject qgidDict;
    }
}
